package com.qm.console.service.bx.impl;

import com.alibaba.fastjson.JSON;
import com.qm.console.component.exception.BusinessException;
import com.qm.console.component.redis.queue.RedisQueue;
import com.qm.console.controller.dto.ResultEnum;
import com.qm.console.controller.vo.comm.query.TimeOffsetQuery;
import com.qm.console.dao.mapper.BxDictDOMapper;
import com.qm.console.dao.mapper.BxAtomFieldDOMapper;
import com.qm.console.dao.mapper.BxDisplayFieldDOMapper;
import com.qm.console.dao.mapper.BxJobDOMapper;
import com.qm.console.dao.model.BxDictDO;
import com.qm.console.dao.model.BxDictDOExample;
import com.qm.console.dao.model.BxAtomFieldDO;
import com.qm.console.dao.model.BxAtomFieldDOExample;
import com.qm.console.dao.model.BxDisplayFieldDO;
import com.qm.console.dao.model.BxDisplayFieldDOExample;
import com.qm.console.dao.model.BxJobDO;
import com.qm.console.dao.model.BxJobDOExample;
import com.qm.console.pubdef.constant.bx.BxJobState;
import com.qm.console.pubdef.dto.bx.BxAtomFieldDTO;
import com.qm.console.pubdef.dto.bx.BxDisplayFieldDTO;
import com.qm.console.pubdef.dto.bx.BxJobDTO;
import com.qm.console.pubdef.dto.bx.BxRunJobDTO;
import com.qm.console.service.bx.BxService;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * //                       _oo0oo_
 * //                      o8888888o
 * //                      88" . "88
 * //                      (| -_- |)
 * //                      0\  =  /0
 * //                    ___/`---'\___
 * //                  .' \\|     |// '.
 * //                 / \\|||  :  |||// \
 * //                / _||||| -:- |||||- \
 * //               |   | \\\  -  /// |   |
 * //               | \_|  ''\---/''  |_/ |
 * //               \  .-\__  '-'  ___/-. /
 * //             ___'. .'  /--.--\  `. .'___
 * //          ."" '<  `.___\_<|>_/___.' >' "".
 * //         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
 * //         \  \ `_.   \_ __\ /__ _/   .-` /  /
 * //     =====`-.____`.___ \_____/___.-`___.-'=====
 * //                       `=---='
 * //
 * //
 * //     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * //
 * //               佛祖保佑         永无BUG
 * <p>
 * com.qm.console.service.bx.impl
 * Created on 2021/1/8.
 */
@Service
public class BxServiceImpl implements BxService {

    @Autowired
    private BxDictDOMapper bxDictDOMapper;
    @Autowired
    private BxJobDOMapper bxJobDOMapper;
    @Autowired
    private BxAtomFieldDOMapper bxAtomFieldDOMapper;
    @Autowired
    private BxDisplayFieldDOMapper bxDisplayFieldDOMapper;
    @Autowired
    private RedisQueue redisQueue;


    @Override
    public List<BxDictDO> listDict() {
        BxDictDOExample example = new BxDictDOExample();
        example.createCriteria().andPythonTypeNotEqualTo("dict");
        return bxDictDOMapper.selectByExample(example);
    }

    @Override
    public List<BxDictDO> listNumericDict() {
        BxDictDOExample example = new BxDictDOExample();
        example.createCriteria().andPythonTypeNotEqualTo("dict").andPythonTypeIn(Arrays.asList("int", "float"));
        return bxDictDOMapper.selectByExample(example);
    }

    @Override
    public void updateDictDisplay(Integer id, String display) {
        BxDictDO update = new BxDictDO();
        update.setId(id);
        update.setDesc(display);
        bxDictDOMapper.updateByPrimaryKeySelective(update);
    }

    @Override
    public List<BxJobDTO> listJobs() {
        BxJobDOExample example = new BxJobDOExample();
        List<BxJobDO> result = bxJobDOMapper.selectByExampleWithBLOBs(example);
        return result.stream().map(item -> BxJobDTO.fromDO(item, bxDisplayFieldDOMapper, bxAtomFieldDOMapper)).collect(Collectors.toList());
    }

    @Override
    public void addJob(BxJobDTO job) {
        job.validate(bxAtomFieldDOMapper, bxDisplayFieldDOMapper);
        BxJobDO item = job.toDO();
        bxJobDOMapper.insertSelective(item);
    }

    @Override
    public String preUpdateJob(BxJobDTO job) {
        if (job.getId() == null || StringUtils.isEmpty(job.getJobName())) {
            throw new BusinessException(ResultEnum.BX_JOB_INVALIDATE);
        }
        BxJobDO current = bxJobDOMapper.selectByPrimaryKey(job.getId());
        if (current == null) {
            throw new BusinessException(ResultEnum.BX_JOB_NOT_EXISTS);
        }
        job.validate(bxAtomFieldDOMapper, bxDisplayFieldDOMapper);

        BxJobDTO currentDTO = BxJobDTO.fromDO(current, bxDisplayFieldDOMapper, bxAtomFieldDOMapper);

        //比较grouper是否发生变化,
        List<String> groupers = new ArrayList<>(currentDTO.getGroupers());
        List<String> newGroupers = new ArrayList<>(job.getGroupers());
        Collections.sort(groupers);
        Collections.sort(newGroupers);

        if (!groupers.equals(newGroupers)) {
            return "任务的筛选器发生变化，将会删除原有的计算数据并重新计算";
        }

        Set<Integer> currentAtomFields = listAtomIds(currentDTO.getDisplayFields());
        Set<Integer> newAtomFields = listAtomIds(job.getDisplayFields());

        if (!currentAtomFields.equals(newAtomFields)) {
            return "任务依赖的基础字段集发生变化，将会删除原有的计算数据并重新计算";
        }
        return "";
    }

    @Override
    public void updateJob(BxJobDTO job) {
        String preUpdate = preUpdateJob(job);
        BxJobDO newJob = job.toDO();
        if (!StringUtils.isEmpty(preUpdate)) {
            newJob.setNeedRefreshTable(true);
        }
        bxJobDOMapper.updateByPrimaryKeySelective(newJob);
    }

    @Override
    public void deleteJob(Integer id) {
        bxJobDOMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void runJobOnce(BxRunJobDTO query) {
        redisQueue.sendMessage("bx.jobs.oneshot", JSON.toJSONString(query));
    }

    @Override
    public void setJobOnline(Integer id) {
        BxJobDO current = bxJobDOMapper.selectByPrimaryKey(id);
        if (current == null) {
            throw new BusinessException(ResultEnum.BX_JOB_NOT_EXISTS);
        }
        if (current.getState() == BxJobState.RUNNING) {
            return;
        }
        if (current.getState() < BxJobState.TEST_PASSED) {
            return;
        }
        current.setState(BxJobState.RUNNING);
        bxJobDOMapper.updateByPrimaryKey(current);
    }

    @Override
    public void setJobOffline(Integer id) {
        BxJobDO current = bxJobDOMapper.selectByPrimaryKey(id);
        if (current.getState() == BxJobState.RUNNING) {
            current.setState(BxJobState.TEST_PASSED); // 专业的要账公司，写到别人名下了,. 东山再起
            bxJobDOMapper.updateByPrimaryKey(current);
        }
    }


    @Override
    public List<BxAtomFieldDTO> listAtomField() {
        BxAtomFieldDOExample example = new BxAtomFieldDOExample();
        List<BxAtomFieldDO> result = bxAtomFieldDOMapper.selectByExampleWithBLOBs(example);
        return result.stream().map(BxAtomFieldDTO::fromDO).collect(Collectors.toList());
    }

    @Override
    public void addAtomField(BxAtomFieldDTO field) {
        field.validate(bxDictDOMapper);
        BxAtomFieldDO add = field.toDO();
        int count = bxAtomFieldDOMapper.insertSelective(add);
        if (count > 0) {
            BxAtomFieldDOExample example = new BxAtomFieldDOExample();
            example.createCriteria().andFieldNameEqualTo(field.getFieldName());
            List<BxAtomFieldDO> insert = bxAtomFieldDOMapper.selectByExample(example);
            BxAtomFieldDO item = insert.get(0);
            if (field.getAddDefaultDisplayField() != null && field.getAddDefaultDisplayField()) {
                BxDisplayFieldDTO displayFieldDTO = new BxDisplayFieldDTO();
                field.setId(item.getId());
                displayFieldDTO.setRelyOn(Collections.singletonList(field));
                displayFieldDTO.setFieldName(field.getFieldName());
                displayFieldDTO.setDesc(field.getDesc());
                displayFieldDTO.setQuery(String.format("sum(%s)", field.getFieldName()));
                this.addDisplayField(displayFieldDTO);
            }
        }

    }

    @Override
    public String preUpdateAtomField(BxAtomFieldDTO field) {
        if (field.getId() == null) {
            throw new BusinessException(ResultEnum.BX_ATOM_VALIDATE_FAILED);
        }
        BxAtomFieldDO currentDO = bxAtomFieldDOMapper.selectByPrimaryKey(field.getId());
        if (currentDO == null) {
            throw new BusinessException(ResultEnum.BX_ATOM_VALIDATE_FAILED);
        }
        if (!StringUtils.equals(field.getFieldName(), currentDO.getFieldName())) {
            throw new BusinessException(ResultEnum.BX_ATOM_FIELD_NAME_CAN_NOT_CHANGE);
        }
        BxAtomFieldDTO current = BxAtomFieldDTO.fromDO(currentDO);
        if (!Objects.equals(current.getConditions(), field.getConditions())
                || !Objects.equals(current.getRelyOn(), field.getRelyOn())
                || !Objects.equals(current.getFormula(), field.getFormula())) {
            List<BxDisplayFieldDO> related = bxDisplayFieldDOMapper.listAtomFieldRelated(String.valueOf(field.getId()));
//            if (related > 0) {
//                throw new BusinessException(ResultEnum.BX_JOB_FIELD_IN_USE);
//            }
            final List<BxJobDO> jobs = new ArrayList<>();
            if (related.size() > 0) {
                related.forEach(item -> {
                    List<BxJobDO> relatedJobs = bxJobDOMapper.listDisplayFieldRelated(String.valueOf(item.getId()));
                    jobs.addAll(relatedJobs);
                });
            }
            String relatedJobs = jobs.stream().map(item -> {
                return String.format("id: %s, name: %s", item.getId(), item.getJobName());
            }).collect(Collectors.joining("\n"));
            return "更新字段将会导致以下任务的数据重置: \n" + relatedJobs;
        }
        return null;
    }

    @Override
    public void updateAtomField(BxAtomFieldDTO field) {
        String preUpdate = preUpdateAtomField(field);
        if (!StringUtils.isEmpty(preUpdate)) {
            List<BxDisplayFieldDO> related = bxDisplayFieldDOMapper.listAtomFieldRelated(String.valueOf(field.getId()));
            if (related.size() > 0) {
                related.forEach(item -> {
                    List<BxJobDO> relatedJobs = bxJobDOMapper.listDisplayFieldRelated(String.valueOf(item.getId()));
                    relatedJobs.forEach(job -> {
                        BxJobDO update = new BxJobDO();
                        update.setId(job.getId());
                        update.setNeedRefreshTable(true);
                        update.setState(BxJobState.WAIT_TEST);
                        bxJobDOMapper.updateByPrimaryKeySelective(update);
                    });
                });
            }
        }
        BxAtomFieldDO update = field.toDO();
        bxAtomFieldDOMapper.updateByPrimaryKeySelective(update);
    }

    @Override
    public void deleteAtomField(Integer id) {
        List<BxDisplayFieldDO> related = bxDisplayFieldDOMapper.listAtomFieldRelated(String.valueOf(id));
        if (related.size() > 0) {
            throw new BusinessException(ResultEnum.BX_JOB_FIELD_IN_USE);
        }
        BxAtomFieldDO item = new BxAtomFieldDO();
        item.setId(id);
        item.setDeleted(true);
        bxAtomFieldDOMapper.deleteByPrimaryKey(id);
    }

    @Override
    public List<BxDisplayFieldDTO> listDisplayField() {
        BxDisplayFieldDOExample example = new BxDisplayFieldDOExample();
        example.createCriteria().andDeletedEqualTo(false);
        List<BxDisplayFieldDO> result = bxDisplayFieldDOMapper.selectByExample(example);
        return result.stream().map(item -> BxDisplayFieldDTO.fromDO(item, bxAtomFieldDOMapper)).collect(Collectors.toList());
    }

    @Override
    public void addDisplayField(BxDisplayFieldDTO field) {
        field.validate(bxAtomFieldDOMapper);
        BxDisplayFieldDO item = field.toDo();
        bxDisplayFieldDOMapper.insertSelective(item);
    }

    @Override
    public String preUpdateDisplayField(BxDisplayFieldDTO field) {
        if (field.getId() == null || field.getFieldName() == null || field.getQuery() == null || field.getRelyOn() == null) {
            throw new BusinessException(ResultEnum.BX_JOB_FIELD_INVALIDATE);
        }
        BxDisplayFieldDO current = bxDisplayFieldDOMapper.selectByPrimaryKey(field.getId());
        if (current == null) {
            throw new BusinessException(ResultEnum.BX_JOB_FIELD_INVALIDATE);
        }
        List<Integer> relyOnIds = field.getRelyOn().stream().map(BxAtomFieldDTO::getId).collect(Collectors.toList());
        List<Integer> currentIds = Arrays.stream(current.getRelyOn().split(",")).map(Integer::parseInt).collect(Collectors.toList());
        Collections.sort(relyOnIds);
        Collections.sort(currentIds);
        if (!relyOnIds.equals(currentIds)) {
            List<BxJobDO> related = bxJobDOMapper.listDisplayFieldRelated(String.valueOf(field.getId()));
            if (related.size() > 0) {
                String jobs = related.stream().map(BxJobDO::getJobName).collect(Collectors.joining(","));
                return String.format("修改依赖字段将会影响这些任务: %s", jobs);
            }
        }
        // todo 可以在这里检查查询语句是否正确
//        }
        return "";
    }

    @Override
    public void updateDisplayField(BxDisplayFieldDTO field) {
        if (!StringUtils.isEmpty(preUpdateDisplayField(field))) {
            List<BxJobDO> related = bxJobDOMapper.listDisplayFieldRelated(String.valueOf(field.getId()));
            related.forEach(item -> {
                BxJobDO update = new BxJobDO();
                update.setId(item.getId());
                update.setNeedRefreshTable(true);
                bxJobDOMapper.updateByPrimaryKeySelective(update);
            });
        }
        BxDisplayFieldDO item = field.toDo();
        bxDisplayFieldDOMapper.updateByPrimaryKeySelective(item);
    }

    @Override
    public void deleteDisplayField(Integer id) {
        List<BxJobDO> related = bxJobDOMapper.listDisplayFieldRelated(String.valueOf(id));
        for (BxJobDO item : related) {
            if (item.getState() == BxJobState.RUNNING || item.getState() == BxJobState.TESTING) {
                throw new BusinessException(ResultEnum.BX_JOB_FIELD_IN_USE);
            }
        }
        bxDisplayFieldDOMapper.deleteByPrimaryKey(id);
    }

    private Set<Integer> listAtomIds(List<BxDisplayFieldDTO> list) {
        final Set<Integer> ids = new HashSet<>();
        list.forEach(item -> {
            List<Integer> atomIds = item.getRelyOn().stream().map(BxAtomFieldDTO::getId).collect(Collectors.toList());
            ids.addAll(atomIds);
        });
        return ids;
    }
}
